package com.tth.learn.addproperties.java;

import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 动态代理举例
 *
 * 要想实现动态代理，需要解决如下两个问题：
 *  1.如何根据被代理类创建一个代理类？
 *  2.如何在调用代理类a的方法时自动调用被代理类的a方法？
 * @Author tth
 * @Date 2022/12/30 14:42
 */
public class DynamicProxyTest {
    @Test
    public void test() {
        // 创建被代理类
        NikeClothFactory1 nike = new NikeClothFactory1();

        // 创建代理类
        ClothFactory1 clothFactory = (ClothFactory1) ProxyFactory.getProxyInstance(nike);

        // 调用服装生产方法
        clothFactory.produceCloth();
    }
}

/**
 * 服装工厂接口：定义生产衣服的接口
 */
interface ClothFactory1 {

    void produceCloth();
}

/**
 * 被代理类
 */
class NikeClothFactory1 implements ClothFactory1 {
    @Override
    public void produceCloth() {
        System.out.println("Nike服装加工厂生产一批运动服");
    }
}


/**
 * 代理工厂类
 *  用来解决“根据被代理类创建代理类”的问题
 */
class ProxyFactory {

    /**
     * 根据被代理类对象创建代理对象--Proxy.newProxyInstance()实现
     * @param obj 被代理类对象
     * @return 代理类对象
     */
    public static Object getProxyInstance(Object obj) {
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                handler);
    }
}

/**
 * 创建MyInvocationHandler实现InvocationHandler，并实现InvocationHandler的invoke()
 * 用来解决“在调用代理类a方法时动态的去调用被代理类的a方法”问题
 */
class MyInvocationHandler implements InvocationHandler {

    // 被代理类对象
    private Object obj;

    /**
     * 被绑定代理类对象（该功能也可以使用构造方法实现）
     * @param obj
     */
    public void bind(Object obj) {
        this.obj = obj;
    }

    /**
     * 当通过代理类对象，调用方法a时，会自动的调用如下方法invoke()
     * @param proxy 代理类对象
     * @param method 代理类要调用的方法
     * @param args 代理类要调用方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(proxy.getClass().getName());
        // method：代理类要调用的方法，同时，此方法也就作为了被代理类要调用的方法
        // method.invoke()的返回值就作为当前类invoke()方法的返回值
        return method.invoke(obj, args);
    }
}